{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "from keras.models import Sequential,Model,load_model\n",
    "from keras.optimizers import SGD\n",
    "from keras.layers import BatchNormalization, Lambda, Input, Dense, Convolution2D, MaxPooling2D, AveragePooling2D, ZeroPadding2D, Dropout, Flatten, merge, Reshape, Activation\n",
    "from keras.layers.merge import Concatenate\n",
    "from keras.preprocessing.image import ImageDataGenerator\n",
    "from keras.callbacks import ModelCheckpoint\n",
    "import numpy as np\n",
    "import keras.backend as K"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def color_net(num_classes):\n",
    "    # placeholder for input image\n",
    "    input_image = Input(shape=(224,224,3))\n",
    "    # ============================================= TOP BRANCH ===================================================\n",
    "    # first top convolution layer\n",
    "    top_conv1 = Convolution2D(filters=48,kernel_size=(11,11),strides=(4,4),\n",
    "                              input_shape=(224,224,3),activation='relu')(input_image)\n",
    "    top_conv1 = BatchNormalization()(top_conv1)\n",
    "    top_conv1 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(top_conv1)\n",
    "\n",
    "    # second top convolution layer\n",
    "    # split feature map by half\n",
    "    top_top_conv2 = Lambda(lambda x : x[:,:,:,:24])(top_conv1)\n",
    "    top_bot_conv2 = Lambda(lambda x : x[:,:,:,24:])(top_conv1)\n",
    "\n",
    "    top_top_conv2 = Convolution2D(filters=64,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(top_top_conv2)\n",
    "    top_top_conv2 = BatchNormalization()(top_top_conv2)\n",
    "    top_top_conv2 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(top_top_conv2)\n",
    "\n",
    "    top_bot_conv2 = Convolution2D(filters=64,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(top_bot_conv2)\n",
    "    top_bot_conv2 = BatchNormalization()(top_bot_conv2)\n",
    "    top_bot_conv2 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(top_bot_conv2)\n",
    "\n",
    "    # third top convolution layer\n",
    "    # concat 2 feature map\n",
    "    top_conv3 = Concatenate()([top_top_conv2,top_bot_conv2])\n",
    "    top_conv3 = Convolution2D(filters=192,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(top_conv3)\n",
    "\n",
    "    # fourth top convolution layer\n",
    "    # split feature map by half\n",
    "    top_top_conv4 = Lambda(lambda x : x[:,:,:,:96])(top_conv3)\n",
    "    top_bot_conv4 = Lambda(lambda x : x[:,:,:,96:])(top_conv3)\n",
    "\n",
    "    top_top_conv4 = Convolution2D(filters=96,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(top_top_conv4)\n",
    "    top_bot_conv4 = Convolution2D(filters=96,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(top_bot_conv4)\n",
    "\n",
    "    # fifth top convolution layer\n",
    "    top_top_conv5 = Convolution2D(filters=64,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(top_top_conv4)\n",
    "    top_top_conv5 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(top_top_conv5) \n",
    "\n",
    "    top_bot_conv5 = Convolution2D(filters=64,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(top_bot_conv4)\n",
    "    top_bot_conv5 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(top_bot_conv5)\n",
    "\n",
    "    # ============================================= TOP BOTTOM ===================================================\n",
    "    # first bottom convolution layer\n",
    "    bottom_conv1 = Convolution2D(filters=48,kernel_size=(11,11),strides=(4,4),\n",
    "                              input_shape=(227,227,3),activation='relu')(input_image)\n",
    "    bottom_conv1 = BatchNormalization()(bottom_conv1)\n",
    "    bottom_conv1 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(bottom_conv1)\n",
    "\n",
    "    # second bottom convolution layer\n",
    "    # split feature map by half\n",
    "    bottom_top_conv2 = Lambda(lambda x : x[:,:,:,:24])(bottom_conv1)\n",
    "    bottom_bot_conv2 = Lambda(lambda x : x[:,:,:,24:])(bottom_conv1)\n",
    "\n",
    "    bottom_top_conv2 = Convolution2D(filters=64,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(bottom_top_conv2)\n",
    "    bottom_top_conv2 = BatchNormalization()(bottom_top_conv2)\n",
    "    bottom_top_conv2 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(bottom_top_conv2)\n",
    "\n",
    "    bottom_bot_conv2 = Convolution2D(filters=64,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(bottom_bot_conv2)\n",
    "    bottom_bot_conv2 = BatchNormalization()(bottom_bot_conv2)\n",
    "    bottom_bot_conv2 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(bottom_bot_conv2)\n",
    "\n",
    "    # third bottom convolution layer\n",
    "    # concat 2 feature map\n",
    "    bottom_conv3 = Concatenate()([bottom_top_conv2,bottom_bot_conv2])\n",
    "    bottom_conv3 = Convolution2D(filters=192,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(bottom_conv3)\n",
    "\n",
    "    # fourth bottom convolution layer\n",
    "    # split feature map by half\n",
    "    bottom_top_conv4 = Lambda(lambda x : x[:,:,:,:96])(bottom_conv3)\n",
    "    bottom_bot_conv4 = Lambda(lambda x : x[:,:,:,96:])(bottom_conv3)\n",
    "\n",
    "    bottom_top_conv4 = Convolution2D(filters=96,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(bottom_top_conv4)\n",
    "    bottom_bot_conv4 = Convolution2D(filters=96,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(bottom_bot_conv4)\n",
    "\n",
    "    # fifth bottom convolution layer\n",
    "    bottom_top_conv5 = Convolution2D(filters=64,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(bottom_top_conv4)\n",
    "    bottom_top_conv5 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(bottom_top_conv5) \n",
    "\n",
    "    bottom_bot_conv5 = Convolution2D(filters=64,kernel_size=(3,3),strides=(1,1),activation='relu',padding='same')(bottom_bot_conv4)\n",
    "    bottom_bot_conv5 = MaxPooling2D(pool_size=(3,3),strides=(2,2))(bottom_bot_conv5)\n",
    "\n",
    "    # ======================================== CONCATENATE TOP AND BOTTOM BRANCH =================================\n",
    "    conv_output = Concatenate()([top_top_conv5,top_bot_conv5,bottom_top_conv5,bottom_bot_conv5])\n",
    "\n",
    "    # Flatten\n",
    "    flatten = Flatten()(conv_output)\n",
    "\n",
    "    # Fully-connected layer\n",
    "    FC_1 = Dense(units=4096, activation='relu')(flatten)\n",
    "    FC_1 = Dropout(0.6)(FC_1)\n",
    "    FC_2 = Dense(units=4096, activation='relu')(FC_1)\n",
    "    FC_2 = Dropout(0.6)(FC_2)\n",
    "    output = Dense(units=num_classes, activation='softmax')(FC_2)\n",
    "    \n",
    "    model = Model(inputs=input_image,outputs=output)\n",
    "    sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)\n",
    "    # sgd = SGD(lr=0.01, momentum=0.9, decay=0.0005, nesterov=True)\n",
    "    model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])\n",
    "    \n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 12483 images belonging to 8 classes.\n",
      "Found 3118 images belonging to 8 classes.\n",
      "Epoch 1/5\n",
      "11999/12000 [============================>.] - ETA: 0s - loss: 0.2570 - acc: 0.9071Epoch 00000: val_acc improved from -inf to 0.86160, saving model to beer_net_color_weights.hdf5\n",
      "12000/12000 [==============================] - 3404s - loss: 0.2570 - acc: 0.9071 - val_loss: 0.3841 - val_acc: 0.8616\n",
      "Epoch 2/5\n",
      "11999/12000 [============================>.] - ETA: 0s - loss: 0.1378 - acc: 0.9483Epoch 00001: val_acc did not improve\n",
      "12000/12000 [==============================] - 3357s - loss: 0.1378 - acc: 0.9483 - val_loss: 0.5655 - val_acc: 0.8428\n",
      "Epoch 3/5\n",
      "11999/12000 [============================>.] - ETA: 0s - loss: 0.1019 - acc: 0.9609Epoch 00002: val_acc improved from 0.86160 to 0.86803, saving model to beer_net_color_weights.hdf5\n",
      "12000/12000 [==============================] - 3361s - loss: 0.1019 - acc: 0.9609 - val_loss: 0.4238 - val_acc: 0.8680\n",
      "Epoch 4/5\n",
      "11999/12000 [============================>.] - ETA: 0s - loss: 0.0781 - acc: 0.9697Epoch 00003: val_acc did not improve\n",
      "12000/12000 [==============================] - 3350s - loss: 0.0781 - acc: 0.9697 - val_loss: 0.7076 - val_acc: 0.8491\n",
      "Epoch 5/5\n",
      "11999/12000 [============================>.] - ETA: 0s - loss: 0.0636 - acc: 0.9754Epoch 00004: val_acc improved from 0.86803 to 0.88031, saving model to beer_net_color_weights.hdf5\n",
      "12000/12000 [==============================] - 3355s - loss: 0.0636 - acc: 0.9754 - val_loss: 0.5297 - val_acc: 0.8803\n"
     ]
    }
   ],
   "source": [
    "img_rows , img_cols = 227,227\n",
    "num_classes = 8\n",
    "batch_size = 32\n",
    "nb_epoch = 5\n",
    "\n",
    "# initialise model\n",
    "model = color_net(num_classes)\n",
    "\n",
    "filepath = 'color_weights.hdf5'\n",
    "checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True, mode='max')\n",
    "callbacks_list = [checkpoint]\n",
    "\n",
    "train_datagen = ImageDataGenerator(\n",
    "        rescale=1./255,\n",
    "        shear_range=0.2,\n",
    "        zoom_range=0.3,\n",
    "        horizontal_flip=True)\n",
    "test_datagen = ImageDataGenerator(rescale=1./255)\n",
    "\n",
    "training_set = train_datagen.flow_from_directory(\n",
    "            'train/',\n",
    "            target_size=(img_rows, img_cols),\n",
    "            batch_size=batch_size,\n",
    "            class_mode='categorical')\n",
    "test_set = test_datagen.flow_from_directory(\n",
    "            'test/',\n",
    "            target_size=(img_rows, img_cols),\n",
    "            batch_size=batch_size,\n",
    "            class_mode='categorical')\n",
    "\n",
    "model.fit_generator(\n",
    "        training_set,\n",
    "        steps_per_epoch=12000,\n",
    "        epochs=nb_epoch,\n",
    "        validation_data=test_set,\n",
    "        validation_steps=3000,\n",
    "        callbacks=callbacks_list)\n",
    "\n",
    "model.save('color_model.h5')\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
